﻿using System;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Windows.Forms;
using EnvDTE;
using EnvDTE80;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Framework.Client;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.CommandBars;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.TeamFoundation;
using TfsGiggle.Core;

namespace TfsGiggle.Search
{
    /// <summary>
    /// Provides search UI for TfsGiggle searches
    /// </summary>
    [PackageRegistration(UseManagedResourcesOnly = true)]
    [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)]
    [ProvideMenuResource("Menus.ctmenu", 1)]
    [ProvideAutoLoad(Microsoft.VisualStudio.Shell.Interop.UIContextGuids80.NoSolution)]
    [ProvideToolWindow(typeof(TfsGiggleSearchToolWindow))]
    [ProvideToolWindow(typeof(TfsGiggleWorkitemWindow))]
    [ProvideToolWindow(typeof(TfsGiggleChangesetWindow))]
    [Guid(GuidList.guid_TfsGiggleSearchPkgString)]
    public sealed class TfsGiggleSearchPackage : Package, IVsShellPropertyEvents
    {
        private const string SearchToken = "[search]";
        private const string TeamFoundationServerExtService = "Microsoft.VisualStudio.TeamFoundation.TeamFoundationServerExt";
        private uint _EventSinkCookie;
        private string currentMRUComboChoice = null;
        private DTE2 dte2;
        private static IVsStatusbar StatusBar;

        /// <summary>
        /// This
        /// </summary>
        internal static TfsGiggleSearchPackage Instance;

        /// <summary>
        /// The TfsGiggle search package
        /// </summary>
        public TfsGiggleSearchPackage()
        {
            Trace.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering constructor for: {0}", this.ToString()));
            TfsGiggleSearchPackage.Instance = this;
        }

        private string GetSearchUri()
        {
            TeamFoundationServerExt tfsExt = this.GetTFSExt();
            if (tfsExt == null)
            {
                return null;
            }
            var tfsProjectCollectionUri = tfsExt.ActiveProjectContext.DomainUri;
            Trace.TraceInformation("SearchPackage:Found tfsProjectCollectionUri:'{0}'", tfsProjectCollectionUri);
            return tfsProjectCollectionUri;
        }

        /// <summary>
        /// This function is called when the user clicks the menu item that shows the 
        /// tool window. See the Initialize method to see how the menu item is associated to 
        /// this function using the OleMenuCommandService service and the MenuCommand class.
        /// </summary>
        private void ShowToolWindows(object sender, EventArgs e)
        {
            Trace.TraceInformation("ShowToolWindows");
            var window = GetToolWindow(typeof(TfsGiggleSearchToolWindow));
            if (window != null)
            {
                Trace.TraceInformation("ShowToolWindows window found");
                IVsWindowFrame windowFrame = (IVsWindowFrame)window.Frame;
                if (windowFrame != null)
                {
                    Trace.TraceInformation("ShowToolWindows frame found");
                    Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(windowFrame.Show());
                }
            }

            window = GetToolWindow(typeof(TfsGiggleWorkitemWindow));
            if (window != null)
            {
                Trace.TraceInformation("ShowToolWindows workitem window found");
                IVsWindowFrame windowFrame = (IVsWindowFrame)window.Frame;
                if (windowFrame != null)
                {
                    Trace.TraceInformation("ShowToolWindows workitem frame found");
                    Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(windowFrame.Show());
                }
            }

            window = GetToolWindow(typeof(TfsGiggleChangesetWindow));
            if (window != null)
            {
                Trace.TraceInformation("ShowToolWindows changeset window found");
                IVsWindowFrame windowFrame = (IVsWindowFrame)window.Frame;
                if (windowFrame != null)
                {
                    Trace.TraceInformation("ShowToolWindows workitem frame found");
                    Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(windowFrame.Show());
                }
            }
        }

        private ToolWindowPane GetToolWindow(Type windowType)
        {
            // Get the instance number 0 of this tool window. This window is single instance so this instance
            // is actually the only one.
            // The last flag is set to true so that if the tool window does not exists it will be created.
            ToolWindowPane window = this.FindToolWindow(windowType, 0, true);
            if ((null == window) || (null == window.Frame))
            {
                Trace.TraceError("GetToolWindow ToolWindow not found");
                throw new NotSupportedException(Resources.CanNotCreateWindow);
            }
            return window;
        }

        /// <summary>
        /// Listen to something
        /// </summary>
        /// <param name="propid"></param>
        /// <param name="var"></param>
        /// <returns></returns>
        public int OnShellPropertyChange(int propid, object var)
        {
            // --- We handle the event if zombie state changes from true to false
            if ((int)__VSSPROPID.VSSPROPID_Zombie == propid)
            {
                if ((bool)var == false)
                {
                    // --- Show the commandbar
                    var cbs = ((CommandBars)dte2.CommandBars);
                    CommandBar cb = cbs["TfsGiggle Search"];
                    cb.Visible = true;

                    // --- Unsubscribe from events
                    var shellService = GetService(typeof(SVsShell)) as IVsShell;
                    if (shellService != null)
                    {
                        ErrorHandler.ThrowOnFailure(shellService.UnadviseShellPropertyChanges(_EventSinkCookie));
                    }

                    _EventSinkCookie = 0;
                }
            }

            return VSConstants.S_OK;
        }

        /// <summary>
        /// Start things up
        /// </summary>
        protected override void Initialize()
        {
            Trace.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering Initialize() of: {0}", this.ToString()));
            try
            {
                base.Initialize();

                dte2 = (DTE2)GetService(typeof(DTE));

                OleMenuCommandService mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
                if (null != mcs)
                {
                    CommandID menuMyMRUComboCommandID = new CommandID(GuidList.guid_TfsGiggleSearchCmdSet, (int)PkgCmdIDList.cmdidTfsGiggleSearchCombo);
                    OleMenuCommand menuMyMRUComboCommand = new OleMenuCommand(new EventHandler(OnMenuMyMRUCombo), menuMyMRUComboCommandID);
                    menuMyMRUComboCommand.ParametersDescription = "$"; // accept any argument string
                    mcs.AddCommand(menuMyMRUComboCommand);

                    // Create the command for the tool window
                    CommandID toolwndCommandID = new CommandID(GuidList.guid_TfsGiggleSearchCmdSet, (int)PkgCmdIDList.cmdidTfsGiggleSearchTool);
                    MenuCommand menuToolWin = new MenuCommand(ShowToolWindows, toolwndCommandID);
                    mcs.AddCommand(menuToolWin);
                }

                // -- Set an event listener for shell property changes
                var shellService = GetService(typeof(SVsShell)) as IVsShell;
                if (shellService != null)
                {
                    ErrorHandler.ThrowOnFailure(shellService.AdviseShellPropertyChanges(this, out _EventSinkCookie));
                }
            }
            catch (Exception ex)
            {
                ActivityLog.LogError("Exception caught in the TfsGiggle package", ex.ToString());
                throw;
            }
        }

        private TeamFoundationServerExt GetTFSExt()
        {
            // Get access to the TFS objects
            TeamFoundationServerExt tfsExt = this.dte2.GetObject(TeamFoundationServerExtService) as TeamFoundationServerExt;

            if (tfsExt == null)
            {
                return null;
            }

            // Make sure the user is connected to a Team Foundation Server
            if (tfsExt.ActiveProjectContext.DomainUri == null || tfsExt.ActiveProjectContext.ProjectUri == null)
            {
                MessageBox.Show(Resources.ErrorNotConnected, Resources.ToolbarName, MessageBoxButtons.OK, MessageBoxIcon.Information);
                return null;
            }
            else
            {
                return tfsExt;
            }
        }

        private void OnMenuMyMRUCombo(object sender, EventArgs e)
        {
            if (e == EventArgs.Empty)
            {
                // We should never get here; EventArgs are required.
                throw (new ArgumentException(Resources.EventArgsRequired)); // force an exception to be thrown
            }

            OleMenuCmdEventArgs eventArgs = e as OleMenuCmdEventArgs;

            if (eventArgs != null)
            {
                object input = eventArgs.InValue;
                IntPtr vOut = eventArgs.OutValue;

                if (vOut != IntPtr.Zero && input != null)
                {
                    throw (new ArgumentException(Resources.BothInOutParamsIllegal)); // force an exception to be thrown
                }
                else if (vOut != IntPtr.Zero)
                {
                    // when vOut is non-NULL, the IDE is requesting the current value for the combo
                    Marshal.GetNativeVariantForObject(this.currentMRUComboChoice, vOut);
                }
                else if (input != null)
                {
                    string newChoice = input.ToString();

                    // new value was selected or typed in
                    if (!string.IsNullOrEmpty(newChoice))
                    {
                        this.currentMRUComboChoice = newChoice;
                        RunSearchCommand(this.currentMRUComboChoice);
                    }
                    else
                    {
                        // We should never get here
                        throw (new ArgumentException(Resources.EmptyStringIllegal)); // force an exception to be thrown
                    }
                }
                else
                {
                    throw (new ArgumentException(Resources.BothInOutParamsIllegal)); // force an exception to be thrown
                }
            }
            else
            {
                // We should never get here; EventArgs are required.
                throw (new ArgumentException(Resources.EventArgsRequired)); // force an exception to be thrown
            }
        }

        internal static void SetStatusMessage(string message)
        {
            if (StatusBar == null)
            {
                StatusBar = Package.GetGlobalService(typeof(IVsStatusbar)) as IVsStatusbar;
            }
            Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(StatusBar.SetText(message));
        }

        /// <summary>
        /// Do a search
        /// </summary>
        /// <param name="userQuery"></param>
        internal void RunSearchCommand(string userQuery)
        {
            if (string.IsNullOrWhiteSpace(userQuery)) return;

            try
            {
                var tfsSearchUri = GetSearchUri();
                if (string.IsNullOrWhiteSpace(tfsSearchUri))
                {
                    return;
                }

                // Normalize the user's input
                string check = userQuery.Trim();
                var result = TfsGiggleSearchPackage.TfsGiggleSearch(tfsSearchUri, check);
                if (result == null || result.Files.Length == 0)
                {
                    Trace.TraceInformation("No results were found");
                    SetStatusMessage(string.Format("No result(s) found for the query '{0}'", check));
                }
                else
                {
                    Trace.TraceInformation("Results were found");
                    SetStatusMessage(string.Format("{0} result(s) found for the query '{1}'", result.Files.Length, check));

#if DEBUG
                    IVsOutputWindow outWindow = Package.GetGlobalService(typeof(SVsOutputWindow)) as IVsOutputWindow;
                    Guid generalPaneGuid = VSConstants.GUID_OutWindowDebugPane; // P.S. There's also the GUID_OutWindowDebugPane available. 
                    IVsOutputWindowPane debugPane;
                    outWindow.GetPane(ref generalPaneGuid, out debugPane);
                    if (debugPane != null)
                    {
                        debugPane.Activate(); // Brings this pane into view 

                        for (int i = 0; i < result.Files.Length; i++)
                        {
                            debugPane.OutputString(result.Files[i].Name + Environment.NewLine);
                        }
                    }
#endif
                    Trace.TraceInformation("ShowToolWindows");
                    ShowToolWindows(null, null);
                    var window = GetToolWindow(typeof(TfsGiggleSearchToolWindow));
                    if (window != null)
                    {
                        Trace.TraceInformation("ShowFiles");
                        var control = window.Content as TfsGiggleSearchControl;
                        if (control != null)
                        {
                            control.TfsGiggleFiles.Clear();
                            for (int i = 0; i < result.Files.Length; i++)
                            {
                                control.TfsGiggleFiles.Add(result.Files[i]);
                            }
                        }
                    }

                    window = GetToolWindow(typeof(TfsGiggleChangesetWindow));
                    if (window != null)
                    {
                        Trace.TraceInformation("ShowChangsets");
                        var changesetControl = window.Content as TfsGiggleChangesetControl;
                        if (changesetControl != null)
                        {
                            changesetControl.TfsGiggleChangesets.Clear();
                            for (int i = 0; i < result.Changesets.Length; i++)
                            {
                                changesetControl.TfsGiggleChangesets.Add(result.Changesets[i]);
                            }
                        }
                    }

                    window = GetToolWindow(typeof(TfsGiggleWorkitemWindow));
                    if (window != null)
                    {
                        Trace.TraceInformation("ShowWorkitems");
                        var workitemControl = window.Content as TfsGiggleWorkitemControl;
                        if (workitemControl != null)
                        {
                            workitemControl.TfsGiggleWorkitems.Clear();
                            for (int i = 0; i < result.Workitems.Length; i++)
                            {
                                workitemControl.TfsGiggleWorkitems.Add(result.Workitems[i]);
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                ActivityLog.LogError("Exception caught in TfsGiggle", ex.ToString());
                throw;
            }
        }

        /// <summary>
        /// Get the filecontents for the document specified
        /// </summary>
        /// <param name="teamProjectCollectionId"></param>
        /// <param name="filePath"></param>
        /// <returns></returns>
        internal string GetFile(string teamProjectCollectionId, string filePath)
        {
            ISearch search = null;
            try
            {
                search = new Client(GetSearchUri()).Search;
                var fileContent = search.GetFile(teamProjectCollectionId, filePath);
                ((IChannel)search).Close();
                return fileContent;
            }
            catch (TimeoutException exception)
            {
                ActivityLog.LogError("Exception caught in TfsGiggle", exception.ToString());
                Trace.TraceError("TimeoutException", exception.ToString());
                if (search != null)
                {
                    ((IChannel)search).Abort();
                }
            }
            catch (CommunicationException exception)
            {
                ActivityLog.LogError("Exception caught in TfsGiggle", exception.ToString());
                Trace.TraceError("CommunicationException", exception);
                if (search != null)
                {
                    ((IChannel)search).Abort();
                }
            }
            return null;
        }

        /// <summary>
        /// Do a TfsGiggle search
        /// </summary>
        /// <param name="tfsSearchUri"></param>
        /// <param name="userQuery"></param>
        /// <returns></returns>
        internal static QueryResultDTO TfsGiggleSearch(string tfsSearchUri, string userQuery)
        {
            ISearch search = null;
            try
            {
                search = new Client(tfsSearchUri).Search;
                var result = search.Query(userQuery);
                ((IChannel)search).Close();
                return result;
            }
            catch (TimeoutException exception)
            {
                ActivityLog.LogError("Exception caught in TfsGiggle", exception.ToString());
                Trace.TraceError("TimeoutException", exception.ToString());
                if (search != null)
                {
                    ((IChannel)search).Abort();
                }
            }
            catch (CommunicationException exception)
            {
                ActivityLog.LogError("Exception caught in TfsGiggle", exception.ToString());
                Trace.TraceError("CommunicationException", exception);
                if (search != null)
                {
                    ((IChannel)search).Abort();
                }
            }
            return null;
        }

        /// <summary>
        /// View the specified artifact
        /// </summary>
        /// <param name="teamProjectCollectionId"></param>
        /// <param name="itemId"></param>
        /// <param name="artifactType"></param>
        internal void ViewArtifact(string teamProjectCollectionId, int itemId, Type artifactType)
        {
            Trace.TraceInformation("ViewArtifact: teamProjectCollectionId:{0} itemId:{1} artifactType:{2}", teamProjectCollectionId, itemId, artifactType);
            var browserService = Package.GetGlobalService(typeof(SVsWebBrowsingService)) as IVsWebBrowsingService;
            if (browserService == null)
            {
                return;
            }

            var tfsUriAsString = GetSearchUri();
            if (string.IsNullOrWhiteSpace(tfsUriAsString))
            {
                return;
            }
            var tfsUri = new Uri(tfsUriAsString);
            var realUrl = string.Format("{0}://{1}:{2}{3}{4}", tfsUri.Scheme, tfsUri.DnsSafeHost, tfsUri.Port, tfsUri.Segments[0], tfsUri.Segments[1]);
            Trace.TraceInformation("ViewArtifact: realUrl:{0}", realUrl);

            var tfsConfigurationServer = TfsConfigurationServerFactory.GetConfigurationServer(new Uri(realUrl));
            tfsConfigurationServer.EnsureAuthenticated();
            var tpcService = tfsConfigurationServer.GetService<ITeamProjectCollectionService>();
            var collection = tpcService.GetCollection(new Guid(teamProjectCollectionId));
            var tfs = tfsConfigurationServer.GetTeamProjectCollection(collection.Id);
            var tswa = tfs.GetService<TswaClientHyperlinkService>();
            Uri uri;
            if (artifactType == typeof(TfsGiggleChangeset))
            {
                uri = tswa.GetChangesetDetailsUrl(itemId);
            }
            else
            {
                uri = tswa.GetWorkItemEditorUrl(itemId);
            }
            Trace.TraceInformation("ViewArtifact: Found a uri:{0}", uri);

            IVsWindowFrame ppFrame;

            // passing 0 to the NavigateFlags allows the browser service to reuse open instances of the internal browser.
            Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(browserService.Navigate(uri.AbsoluteUri, 0, out ppFrame));
        }
    }
}

